/*
 * Copyright (c) 2010-2011 Josef Hardi <josef.hardi@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package id.duapuluhdua.dakidaki.map;

/**
 * A calculator for converting the WGS (Lon, Lat) coordinate to Map Tiling
 * System (Tx, Ty), and vice versa.
 *
 * <pre>
 * 180     270      0      90      180    ==> WGS
 *  --------------------------------- 90
 *  |       |       |       |       |
 *  | (0,0) | (1,0) | (2,0) | (3,0) |
 *  |       |       |       |       |
 *  --------------------------------- 45
 *  |       |       |       |       |
 *  | (0,1) | (1,1) | (2,1) | (3,1) |
 *  |       |       |       |       |
 *  +++++++++++++++++++++++++++++++++ 0 (Equator)
 *  |       |       |       |       |
 *  | (0,2) | (1,2) | (2,2) | (3,2) |
 *  |       |       |       |       |
 *  --------------------------------- -45
 *  |       |       |       |       |
 *  | (0,3) | (1,3) | (2,3) | (3,3) |
 *  |       |       |       |       |
 *  --------------------------------- -90
 *  0       90     180     270      0     ==> Tiling System (zoom: 2)
 *
 *  N-tiles = 2^zoom (above: 2^2 = 4 tiles).
 *  The world map is drawn using 4 segmented tiles in zoom level of two.
 *  </pre>
 */
public class TileUtils
{
  /**
   * Calculates the TileX index (Tx) given the longitude and zoom level.
   *
   * @param zoom
   *            The zoom level.
   * @param lon
   *            The WGS longitude coordinate (in degree).
   * @return The Tx index.
   */
  public static double getTileX(float zoom, double lon)
  {
    if (lon > 180) {
      lon -= 360;
    }
    return (lon + 180d) / 360d * numberOfTiles(zoom);
  }

  /**
   * Calculates the TileY index (Ty) given the latitude and zoom level.
   *
   * @param zoom
   *            The zoom level.
   * @param lat
   *            The WGS latitute coordinate (in degree).
   * @return The Ty index.
   */
  public static double getTileY(float zoom, double lat)
  {
    if (lat > 89.5) {
      lat = 89.5;
    }
    if (lat < -89.5) {
      lat = -89.5;
    }
    double phi = Math.toRadians(lat);
    double eval = Math.log(Math.tan(phi) + 1 / Math.cos(phi));

    return (1 - eval / Math.PI) / 2 * numberOfTiles(zoom);
  }

  /**
   * Calculates the longitude (lon) given the TileX index and zoom level.
   *
   * @param zoom
   *            The zoom level.
   * @param Tx
   *            The TileX index.
   * @return The longitude degree.
   */
  public static double getLongitude(float zoom, double Tx)
  {
    return ((Tx / numberOfTiles(zoom)) * 360.0) - 180.0;
  }

  /**
   * Calculates the latitude (lat) given the TileY index and zoom level.
   *
   * @param zoom
   *            The zoom level.
   * @param Ty
   *            The TileY index.
   * @return The latitude degree.
   */
  public static double getLatitude(float zoom, double Ty)
  {
    int sign = Ty < 0 ? -1 : 1;
    double result = Math.atan(sign
        * Math.sinh(Math.PI * (1 - 2 * Ty / numberOfTiles(zoom))))
        * 180d / Math.PI;
    return result;
  }

  /**
   * Calculates the total number tiles given a zoom level. In general the
   * formula is 2^zoom.
   *
   * @param zoom
   *            The zoom level.
   * @return The total number of tiles.
   */
  private static double numberOfTiles(float zoom)
  {
    if (zoom >= 0 && zoom - Math.floor(zoom) < 0.05f) {
      return 1 << ((int) zoom);
    }
    else {
      return Math.pow(2, zoom);
    }
  }
}
